/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.jfonia.model;

import org.jfonia.connect5.basics.BasicValueNode;
import org.jfonia.connect5.basics.MutableValueNode;
import org.jfonia.connect5.intervals.Interval;
import org.jfonia.connect5.relations.MergeRelation;
import org.jfonia.model.elements.MusicalElement;
import org.jfonia.model.elements.Tone;
import org.jfonia.model.relations.ExclusiveFalseRelation;
import org.jfonia.model.relations.MergePerformanceRelation;

/**
 *
 * @author jannes
 */
public class ToneSequence extends Sequence<Tone>{
    private PerformanceSequence performanceSequence;
    private MutableValueNode<Boolean> lockPerformance;//used for conversion between ticks and nano
    private MutableValueNode<Boolean> lockNotation;  //used for conversion between ticks and nano
    private boolean syncSequences;

    public ToneSequence(){
        super(Tone.class);
        performanceSequence = new PerformanceSequence();
        performanceSequence.addObserver(this);
        lockNotation = new BasicValueNode<Boolean>(true);
        lockPerformance = new BasicValueNode<Boolean>(true);
        new ExclusiveFalseRelation(lockPerformance, lockNotation);
        syncSequences = true;
    }

    @Override
    public void setLockPerformance(boolean lock){
        lockPerformance.setValue(lock);
    }

    public MutableValueNode<Boolean> getLockPerformanceNode(){
        return lockPerformance;
    }

    public MutableValueNode<Boolean> getLockNotationNode(){
        return lockNotation;
    }

    @Override
    public void setLockNotation(boolean lock){
        lockNotation.setValue(lock);
    }

    public PerformanceSequence getPerformanceSequence(){
        return performanceSequence;
    }

    public PerformanceNode getPerformanceNode(MusicalElement musicalElement){
        int index = indexOf(musicalElement);
        return (PerformanceNode)performanceSequence.getIntervalFromIndex(index);
    }

    public PerformanceNode getPerformanceNodeFromIndex(int index){
        return (PerformanceNode)performanceSequence.getIntervalFromIndex(index);
    }

    public PerformanceNode getPerformanceNode(long time){
        return (PerformanceNode)performanceSequence.getIntervalFromValue(time);
    }

    public PerformanceNode add(PerformanceNode perNode){
        return (PerformanceNode)performanceSequence.add(perNode);
    }

    public int indexOf(PerformanceNode perNode){
        return performanceSequence.indexOf(perNode);
    }

    public long getPerformanceLength(){
        return performanceSequence.getDifference();
    }

    public MusicalElement getMusicalElement(PerformanceNode perNode){
        int index = performanceSequence.indexOf(perNode);
        return (MusicalElement)getIntervalFromIndex(index);
    }

    @Override
    public void intervalAdded(Interval interval) {
        if(syncSequences){
            if(interval instanceof MusicalElement){//musical element added
                if(performanceSequence.size() != size()){ //performanceNode needs to be added
                    PerformanceNode performanceNode = new PerformanceNode(0l);
                    performanceSequence.insert(indexOf(interval),performanceNode);
                }
                nodeAdded(this, (MusicalElement)interval);//only inform listener when musicalElement is added
            }else{  //performanceNode added
                if(performanceSequence.size() != size()){ //musicalElement needs to be added
                    try{
                        MusicalElement musicalElement = (MusicalElement) templateClass.newInstance();
                        insert(performanceSequence.indexOf(interval),musicalElement);
                    }catch(Exception exc){}
                }
            }
        }
    }

    @Override
    public void intervalRemoved(Interval interval, int index) {
        if(syncSequences){
            if(performanceSequence.size() != size()){
                if(interval instanceof MusicalElement){
                    performanceSequence.remove(index);
                    nodeRemoved(this,(MusicalElement)interval); //only inform if the musicalElement is removed
                }else{
                    remove(index);
                }
            }
        }
    }

    @Override
    public void intervalEndChanged(Interval interval) {
        if(syncSequences){
            if(interval instanceof MusicalElement){
                nodeDifferenceChanged(this, (MusicalElement)interval);
            }else if(size() == performanceSequence.size()){
                int index = performanceSequence.indexOf(interval);
                nodeDifferenceChanged(this, (MusicalElement)getIntervalFromIndex(index));
            }
        }
    }

    public void shift(long shiftLength){
        lockPerformance.setValue(true);
        lockNotation.setValue(true);//lowers overhead
        if(performanceSequence != null){
            shiftLength %= performanceSequence.getDifference();
            long newEnd = performanceSequence.getDifference()-shiftLength;
            PerformanceNode perNode = (PerformanceNode)performanceSequence.getIntervalFromValue(newEnd);
            int startIndex = performanceSequence.indexOf(perNode);
            PerformanceNode perNodeNew = null;
            MusicalElement musicalElementNew = null;
            MusicalElement musicalElement = null;
            if(perNode.getBegin().longValue() != newEnd){//splitting required;
                perNodeNew = perNode.clone();
                musicalElement = ((MusicalElement)getIntervalFromIndex(startIndex));
                musicalElementNew = musicalElement.clone();
                musicalElement.setDifference(0);
                perNodeNew.getDifferenceNode().setValue(newEnd-perNode.getBegin());
                insert(startIndex,musicalElementNew,perNodeNew);
                perNode.setDifference(perNode.getDifference()-perNodeNew.getDifference());
                startIndex++;
            }
            int size = size();
            MergeRelation.enableMerge.setValue(false);
            for(int i = startIndex; i < size; i++){
                int index = performanceSequence.size()-1;
                PerformanceNode pn = (PerformanceNode)performanceSequence.getIntervalFromIndex(index);
                MusicalElement me = (MusicalElement)getIntervalFromIndex(index);
                performanceSequence.remove(index);
                //intervalRemoved will remove the MusicalElement
                insert(0,me,pn);
            }
            if(perNodeNew != null){
                new MergePerformanceRelation(perNodeNew, perNode, performanceSequence, musicalElementNew, musicalElement);                
            }
        }
        lockNotation.setValue(false);
        MergeRelation.enableMerge.setValue(true);
    }

    @Override
    public void shift(int shiftLength){
        lockNotation.setValue(true);
        lockPerformance.setValue(true);//lowers overhead
        shiftLength %= getDifference();
        int newEnd = getDifference()-shiftLength;
        MusicalElement musicalElement = (MusicalElement)getIntervalFromValue(newEnd);
        int startIndex = indexOf(musicalElement);
        MusicalElement musicalElementNew = null;
        PerformanceNode perNodeNew = null;
        PerformanceNode perNode = null;
        if(musicalElement.getBegin().longValue() != newEnd){//splitting required;
            musicalElementNew = musicalElement.clone();
            if(performanceSequence != null){
                perNode = ((PerformanceNode)performanceSequence.getIntervalFromIndex(startIndex));
                perNodeNew = perNode.clone();
                perNodeNew.setDifference(0l);
            }
            musicalElementNew.getDifferenceNode().setValue(newEnd-musicalElement.getBegin());
            insert(startIndex,musicalElementNew,perNodeNew);
            musicalElement.setDifference(musicalElement.getDifference()-musicalElementNew.getDifference());
            startIndex++;
        }
        int size = size();
        MergeRelation.enableMerge.setValue(false);
        for(int i = startIndex; i < size; i++){
            int index = size()-1;
            MusicalElement me = (MusicalElement)getIntervalFromIndex(index);
            PerformanceNode pn = null;
            if(performanceSequence != null){
                pn = (PerformanceNode)performanceSequence.getIntervalFromIndex(index);
            }
            remove(index);
            //intervalRemoved will remove the MusicalElement
            insert(0,me,pn);
        }
        if(perNodeNew != null){                            
            new MergePerformanceRelation(perNodeNew, perNode, performanceSequence, musicalElementNew, musicalElement);
        }
        lockPerformance.setValue(false);
        MergeRelation.enableMerge.setValue(true);
    }

    public void add(MusicalElement musicalElement, PerformanceNode perNode){
        syncSequences = false;  //we want to manually add each interval.
        add(musicalElement);
        performanceSequence.add(perNode);
        nodeAdded(this, musicalElement); //needed because we disabled syncing
        syncSequences = true;   //resume syncing between sequences
    }

    public void insert(int index, MusicalElement musicalElement, PerformanceNode perNode){
        syncSequences = false;  //we want to manually add each interval.
        insert(index, musicalElement);
        performanceSequence.insert(index, perNode);
        nodeAdded(this, musicalElement); //needed because we disabled syncing
        syncSequences = true;   //resume syncing between sequences
    }


}
